package cds

import (
	"dgo/goutils/utils"
	"math"
)

var cds_header = []byte{
	0x96, 0x19, 0xE0, 0xBD, 0x01, 0x00, 0x00, // 0...6
	0x00, 0x18, 0x00, 0x00, 0x00, // 7..11
	0x01, 0x00, // 12, 13 // field n
	0x00, 0x00, 0x00, 0x00, // 14..17   // recordcount
	0x03, 0x00, 0x00, 0x00, // 18..21
	0xFF, 0x00} // 22, 23 // header len

var cds_pack_end_buf = []byte{0x00, 0x00}

const (
	dsVaryingFldType int32 = 0x00400000
	dsfldZSTRING     int32 = 9
	dsfldINT         int32 = 1
	dsfldBYTES       int32 = 11
	dsSizeBitsLen    int32 = 16

	fldIsChanged   byte = 0
	fldIsNull      byte = 1
	fldIsUnChanged byte = 2
)

type CdsPack struct {
	colcnt     uint16 // 列数量
	bb         utils.BytesBuilder
	rowcnt     int // 行数了
	colidx     int
	rowflagidx int // 行标记 索引
	rowflagbuf []byte
	//rowflag *
}

func (this *CdsPack) BeginHeader() {
	// 00000000h: 96 19 E0 BD 01 00 00 00 18 00 00 00 01 00 A0 86 01 00 03 00 00   // 10W
	this.bb.Write(cds_header)
	//this.bb.ReplaceUInt16_LE(12, uint16(nfield))
	//this.bb.ReplaceUInt32_LE(14, uint32(recordcount)) // A0 86 01 00
}

func EncodeStringFldType(size int) int32 {
	// FldType := FldType shl dsSizeBitsLen or dsVaryingFldType;
	//        if Info.Size < 255 then
	//          FldType := FldType or SizeOf(Byte) else
	//          FldType := FldType or SizeOf(Word);
	v := dsfldZSTRING<<dsSizeBitsLen | dsVaryingFldType
	if size < 255 {
		v = v | 1
	} else {
		v = v | 2
	}
	return v
}

func EncodeBlobFldType() int32 {
	// FldType := (FldType shl dsSizeBitsLen) or dsVaryingFldType or SizeOf(Integer);
	v := dsfldBYTES<<dsSizeBitsLen | dsVaryingFldType | 4
	return v
}

func EncodeIntFldTypeBK() int32 {
	// FldType := FldType shl dsSizeBitsLen or dsVaryingFldType;
	//        if Info.Size < 255 then
	//          FldType := FldType or SizeOf(Byte) else
	//          FldType := FldType or SizeOf(Word);
	v := dsfldZSTRING<<dsSizeBitsLen | dsVaryingFldType
	return v
}

func EncodeIntFldType() int32 {
	v := dsfldINT<<dsSizeBitsLen | 4
	return v
}

/*
**

	字符串
	    ParamType := (dsfldZSTRING shl dsSizeBitsLen) or dsVaryingFldType or SizeOf(Word);
	    Count := Length(TEncoding.UTF8.GetBytes(string(Value)));
	    ParamLen := Count + 1;
	    PWord(FBuffer)^ := ParamLen;
	    Inc(ParamLen, SizeOf(Word));
	    if Count > (Length(FBuffer) - SizeOf(Word)) then
	      Count := Length(FBuffer) - SizeOf(Word);
	    LValuePtr := M.AsUtf8(string(Value)).ToPointer;
	    Move(LValuePtr^, FBuffer[SizeOf(Word)], Count);
	    FBuffer[ParamLen-1] := 0; //Ensure null termination
*/
func EncodeStringAttrBuffer(val string, sb *utils.BytesBuilder) {
	valBuf := []byte(val)
	l := len(valBuf)
	paraml := l + 1
	sb.WriteUInt16_LE(uint16(paraml))
	if l > 8192-2 {
		l = 8192 - 2
		sb.Write(valBuf[:l])
	} else {
		sb.Write(valBuf)
		sb.WriteByte(0) //Ensure null termination
	}
}

/*
*
PutField(fldIsChanged, Info.Size, FBuffer))

	function PutField(          { Add this field to the data stream. }
	    FldStatus: TPcktFldStatus;
	    Len: Cardinal;
	    Src: Pointer
	): DBResult; stdcall;
*/
func (this *CdsPack) putField(fldStatus byte, l uint32, data []byte) {
	this.bb.WriteByte(fldStatus)
	if l > 0 {
		this.bb.WriteUInt32_LE(uint32(l)) // ?
	}
	if len(data) > 0 {
		this.bb.Write(data)
	}
}

/*
*

	szFieldName: MIDASNAME;          { Column Name }  长度(1) + 字符串 最长32位
	iFieldType: Integer;            { Column Type }   4
	iAttributes: Word;              { Column attributes } 2
*/
func (this *CdsPack) WriteFieldDesc(fdname string, fdtye int32, attr uint16) {
	this.WriteUtf8FieldStr(fdname)
	this.bb.Write(utils.UInt32_LEBytes(uint32(fdtye)))
	this.bb.Write(utils.UInt16_LEBytes(attr))
}

func (this *CdsPack) WriteFieldWidthAttr(v int32) {
	this.WriteUtf8FieldStr("WIDTH")
	vty := dsfldINT<<dsSizeBitsLen | 4
	this.bb.Write(utils.UInt32_LEBytes(uint32(vty)))
	this.bb.Write(utils.UInt32_LEBytes(uint32(v)))
}

/*
**
AddAttribute(fldAttrArea, szSUBTYPE, TempStr, False);
*/
func (this *CdsPack) WriteFieldSubtypeAttr(subtype string) {
	this.WriteUtf8FieldStr("SUBTYPE")
	vty := (dsfldZSTRING << dsSizeBitsLen) | dsVaryingFldType | 2
	this.bb.Write(utils.UInt32_LEBytes(uint32(vty)))
	EncodeStringAttrBuffer(subtype, &this.bb)
}

func (this *CdsPack) WriteASCII(str string) *CdsPack {
	strBuf := []byte(str)
	this.bb.WriteByte(byte(len(strBuf))) // 字符长度
	this.bb.Write(strBuf)
	return this
}

func (this *CdsPack) WriteUtf8FieldStr(str string) *CdsPack {
	fieldBuf := []byte(str)
	if len(fieldBuf) > 32 {
		fieldBuf = fieldBuf[:32]
	}
	this.bb.WriteByte(byte(len(str))) // 字符长度
	this.bb.Write(fieldBuf)
	return this
}

func (this *CdsPack) WriteString(str string) *CdsPack {
	fieldBuf, _ := String2ANSIBuf(str)
	this.bb.WriteByte(byte(len(str))) // 字符长度
	this.bb.Write(fieldBuf)
	return this
}

func (this *CdsPack) EndHeader(hasData bool) {
	if this.colcnt > 1 || hasData { // 发现2个以上字段后面会添加00 00
		this.bb.WriteBytes(cds_pack_end_buf...) // endheader
	}

	this.bb.ReplaceUInt16_LE(12, uint16(this.colcnt))
	this.bb.ReplaceUInt16_LE(22, uint16(this.bb.Len()))

	// 一个字段 要占用两bit
	n := int(math.Ceil(float64(this.colcnt) / 4.0))
	this.rowflagbuf = make([]byte, n)
}

/*
*

	写入row开始标记
	// Check(DSWriter.PutField(fldIsChanged, 1, @B));
*/
func (this *CdsPack) WriteRowStartFlag(flag byte) *CdsPack {
	this.bb.WriteByte(flag)
	this.rowflagidx = this.bb.Len()

	this.bb.Write(this.rowflagbuf)

	this.rowcnt++
	this.bb.ReplaceUInt32_LE(14, uint32(this.rowcnt))
	return this
}

/*
*
写入字符串
*/
func (this *CdsPack) WriteRowDataString(str string, maxl int) *CdsPack {
	fieldBuf, _ := String2ANSIBuf(str)
	l := len(str)
	if l > maxl {
		l = maxl
		fieldBuf = fieldBuf[:l]
	}
	if maxl <= 0xFF {
		this.bb.WriteByte(byte(l)) // 字符长度
	} else {
		this.bb.WriteUInt16_LE(uint16(l)) // 字符长度
	}

	if len(fieldBuf) > 0 {
		this.bb.Write(fieldBuf)
	}

	this.colidx++

	return this
}

func (this *CdsPack) WriteRowDataNull() *CdsPack {
	idx, msk := NullMask(this.colidx)
	idx += this.rowflagidx
	this.bb.ByteOr(idx, msk)
	this.colidx++
	return this
}

/*
*
写入数据
*/
func (this *CdsPack) WriteRowDataBuf(data []byte) *CdsPack {
	l := len(data)
	this.bb.WriteUInt32_LE(uint32(l))
	//if l <= 0xFF{
	//	this.bb.WriteByte(byte(l)) // 字符长度
	//}else{
	//	this.bb.WriteUInt16_LE(uint16(l)) // 字符长度
	//}

	if len(data) > 0 {
		this.bb.Write(data)
	}

	return this
}

func (this *CdsPack) WriteTimeField(fn string) {
	// fd1_ilargint, fd2_ftTime
	// 03 66 64 31 08 00 01 00 00 00 00 00
	// 03 66 64 32 04 00 07 00 00 00 00 00 00 00
	this.WriteString(fn)
	this.bb.WriteBytes(0x04, 00, 0x07, 00, 00, 00, 00, 00)
}

func (this *CdsPack) WriteFloatField(fn string) {
	// fd1_ftFloat
	// 03 66 64 31 08 00 04 00 00 00 00 00 00 00
	this.WriteString(fn)
	this.bb.WriteBytes(0x08, 00, 0x04, 00, 00, 00, 00, 00)
	this.colcnt++
}

func (this *CdsPack) WriteDateTimeField(fn string) {
	// fd1_ftDatetime
	// 03 66 64 31 08 00 08 00 00 00 00 00 00 00
	this.WriteString(fn)
	this.bb.WriteBytes(0x08, 00, 0x08, 00, 00, 00, 00, 00)
	this.colcnt++
}

func (this *CdsPack) WriteMemoField(fn string) {
	// fd1_ftWideMemo
	//03 66 64 31 04 00 4B 00 00 00 01 00 07 53 55 42 54 59 50 45 02 00 49 00 ; .....SUBTYPE..I.
	//09 00 57 69 64 65 54 65 78 74 00           ; ..WideText...
	this.WriteString(fn)
	this.bb.WriteBytes(0x04, 00, 0x4B, 00, 00, 00, 01, 00)
	this.WriteString("SUBTYPE")
	this.bb.WriteBytes(0x02, 00, 0x49, 00, 0x09, 00)
	this.bb.WriteBytes([]byte("WideText")...)
	this.bb.WriteByte(00)
	this.colcnt++
}

func (this *CdsPack) WriteBlobField(fn string) {
	// fd1_ftBlob
	// 03 66 64 31 04 00 4B 00 00 00 01 00 07 53 55 42 54 59 50 45 02 00 49 00 ; ......;..fd1..K......SUBTYPE..I.
	// 07 00 42 69 6E 61 72 79 00                ; ..Binary.
	fdtyp := EncodeBlobFldType()
	this.WriteFieldDesc(fn, fdtyp, 0)
	this.bb.WriteUInt16_LE(1) // attr count?
	this.WriteFieldSubtypeAttr("Binary")

	this.colcnt++
}

func (this *CdsPack) WriteIntField(fn string) {
	fdtyp := EncodeIntFldType()
	this.WriteFieldDesc(fn, fdtyp, 0)
	this.colcnt++
}

func (this *CdsPack) WriteStringField(fn string, l int) {
	fdtyp := EncodeStringFldType(l)
	this.WriteFieldDesc(fn, fdtyp, 0)
	this.bb.Write(utils.UInt16_LEBytes(1)) // attr count?
	this.WriteFieldWidthAttr(int32(l))
	this.colcnt++
}

func (this *CdsPack) Data() []byte {
	return this.bb.Bytes()
}
